﻿/*
 * 项目地址：http://code.google.com/p/chineseext/
 * 欢迎参与我们翻译的工作！详见《EXT API2Chinese 相关事宜》：
 * http://bbs.ajaxjs.com Ext中文站翻译小组
 * 
 * 本翻译采用“创作共同约定、Creative Commons”。您可以自由：
 * 复制、发行、展览、表演、放映、广播或通过信息网络传播本作品
 * 创作演绎作品
 * 请遵守：
 *    署名. 您必须按照作者或者许可人指定的方式对作品进行署名。
 * # 对任何再使用或者发行，您都必须向他人清楚地展示本作品使用的许可协议条款。
 * # 如果得到著作权人的许可，您可以不受任何这些条件的限制
 * http://creativecommons.org/licenses/by/2.5/cn/
 */
 
/**
 * @class Ext.data.Record
 * @extends Object
 * <p>
 * Record类不但封装了Record的<em>相关定义</em>信息，还封装了{@link Ext.data.Store}里面所使用的Recrod对象的值信息，
 * 并且方便任何透过{@link Ext.data.Store}来访问Records缓存之信息的代码。<br />
 * Instances of this class encapsulate both Record <em>definition</em> information, and Record
 * <em>value</em> information for use in {@link Ext.data.Store} objects, or any code which needs
 * to access Records cached in an {@link Ext.data.Store} object.</p>
 * <p> * 静态方法{@link #create}可接受一个参数来生成一个属于该类的构造器，这个参数就是字段定义的数组。
 * 只当{@link Ext.data.Reader}处理未格式化的数据对象时，才会创建Record的实例。<br />Constructors for this class are generated by passing an Array of field definition objects to {@link #create}.
 * Instances are usually only created by {@link Ext.data.Reader} implementations when processing unformatted data
 * objects.</p>
 * <p>注意同一时间内一个{@link Ext.data.Store Store}只能携带一个Record类的实例。
 * 要复制出另外一份的数据，就要使用{@link #copy}方法创建Record对象的副本，然后把新实例插入到另外的Store中去。<br />Note that an instance of a Record class may only belong to one {@link Ext.data.Store Store} at a time.
 * In order to copy data from one Store to another, use the {@link #copy} method to create an exact
 * copy of the Record, and insert the new instance into the other Store.</p>
 * <p>当要序列化数据提供至服务器方面的时候，要注意Record的一些私_有属性，还有其所在的Store，反过来说，Store的引用便是一份Record的引用。
 * 也就是说，{@link Ext.util.JSON.encode}不一定会对Record对象编码。这样，就用{@link data}和{@link id}属性。<br />When serializing a Record for submission to the server, be aware that it contains many private
 * properties, and also a reference to its owning Store which in turn holds references to its Records.
 * This means that a whole Record may not be encoded using {@link Ext.util.JSON.encode}. Instead, use the
 * <code>{@link #data}</code> and <code>{@link #id}</code> properties.</p>
 * Record对象经构造器产生后，拥有Ext.data.Record的一切方法，如下列。
 * Record objects generated by this constructor inherit all the methods of Ext.data.Record listed below.
 * @constructor 该构建器不能被用来创建Record对象。取而代之的，是用{@link #create}方法生成的构建器。参数都是相同的。This constructor should not be used to create Record objects. Instead, use the constructor generated by
 * {@link #create}. The parameters are the same.
 * @param {Array} data 对象数组，提供了每一个Record实例所需的字段属性定义。An object, the properties of which provide values for the new Record's fields.
 * @param {Object} id （可选的）记录的id，它应当是独一无二的。{@link Ext.data.Store}会用这个id表示来标识记录里面的Record对象，如不指定就生成一个。
 * (Optional) The id of the Record. This id should be unique, and is used by the
 * {@link Ext.data.Store} object which owns the Record to index its collection of Records. If
 * not specified an integer id is generated.
 */
Ext.data.Record = function(data, id){
    this.id = (id || id === 0) ? id : ++Ext.data.Record.AUTO_ID;
    this.data = data;
};

/**
 * 生成一个构造函数，该函数能产生符合规定的Record对象。
 * Generate a constructor for a specific Record layout.
 * @param {Array} o 数组。各个字段的定义，包括字段名、数据类型（可选的）、映射项（用于在{@link Ext.data.Reader}的数据对象中提取真实的数据）。
 * 每一个字段的定义对象可包含以下的属性：
 * An Array of field definition objects which specify field names, and optionally,
 * data types, and a mapping for an {@link Ext.data.Reader} to extract the field's value from a data object.
 * Each field definition object may contain the following properties: 
 * <ul>
 * <li><b>name</b> : String<div class="sub-desc">
 * Record对象所引用的字段名称。通常是一标识作它者引用，
 * 例如，在列定义中，该值会作为{@link Ext.grid.ColumnModel}的<em>dataIndex</em>属性。
 * The name by which the field is referenced within the Record. This is referenced by,
 * for example, the <em>dataIndex</em> property in column definition objects passed to {@link Ext.grid.ColumnModel}</div></li>
 * 
 * <li><b>mapping</b> : String<div class="sub-desc">
 * （可选的） 如果使用的是{@link Ext.data.Reader}，这是一个Reader能够获取数据对象的数组值创建到Record对象下面的对应的映射项；
 * 如果使用的是{@link Ext.data.JsonReader}，那么这是一个javascript表达式的字符串，
 * 能够获取数据的引用到Record对象的下面；
 * 如果使用的是{@link Ext.data.XmlReader}，这是一个{@link Ext.DomQuery}路径，
 * 能够获取数据元素的引用到Record对象的下面；
 * 如果映射名与字段名都是相同的，那么映射名可以省略。
 * (Optional) A path specification for use by the {@link Ext.data.Reader} implementation
 * that is creating the Record to access the data value from the data object. If an {@link Ext.data.JsonReader}
 * is being used, then this is a string containing the javascript expression to reference the data relative to
 * the Record item's root. If an {@link Ext.data.XmlReader} is being used, this is an {@link Ext.DomQuery} path
 * to the data item relative to the Record element. If the mapping expression is the same as the field name,
 * this may be omitted.</div></li>
 * 
 * 
 * <li><b>type</b> : String<div class="sub-desc">
 * （可选的） 指明数据类型，转化为可显示的值。有效值是：
 * (Optional) The data type for conversion to displayable value. Possible values are
 * <ul><li>auto （auto是默认的，不声明就用auto。不作转换）(Default, implies no conversion)</li>
 * <li>string</li>
 * <li>int</li>
 * <li>float</li>
 * <li>boolean</li>
 * <li>date</li></ul></div></li>
 * 
 * <li><b>dateFormat</b> : String<div class="sub-desc"></div></li>
 * <li><b>defaultValue</b> : Mixed<div class="sub-desc">（可选的）默认值。<b>
 * 当经过{@link Ext.data.Reader Reader}创建Record时会使用该值；</b> 当<b><tt>mapping</tt></b>的引用项不存在的时候，典型的情况为undefined时候会使用该值（默认为''）
 * 
 * 
 * 
 * <li><b>sortType</b> : Function<div class="sub-desc">（可选的） {@link Ext.data.SortTypes}的成语。(Optional) A function which converts a Field's value to a comparable value
 * in order to ensure correct sort ordering. Predefined functions are provided in {@link Ext.data.SortTypes}.</div></li>
 * <li><b>sortDir</b> : String<div class="sub-desc">（可选的） 初始化的排序方向，“ASC”或“DESC”。(Optional) Initial direction to sort. "ASC" or "DESC"</div></li>
 * <li><b>convert</b> : Function<div class="sub-desc">（可选的） 由Reader提供的用于转换值的函数，将值变为Record下面的对象。它会送入以下的参数：
 * (Optional) A function which converts the value provided by the Reader into an object that will be stored in the Record. It is passed the following parameters:<ul>
 * 
 * <li><b>v</b> : Mixed<div class="sub-desc">数据值，和Reader读取的一样。The data value as read by the Reader.</div></li>
 * <li><b>rec</b> : Mixed<div class="sub-desc">包含行的数据对象，和Reader读取的一样。
 * 这可以是数组，对象，XML元素对象，这取决于Reader对象的类型。
 * The data object containing the row as read by the Reader.
 * Depending on Reader type, this could be an Array, an object, or an XML element.</div></li>
 * </ul></div></li>
 * 
 * <li><b>dateFormat</b> : String<div class="sub-desc">（可选的） 字符串格式的{@link Date#parseDate Date.parseDate}函数，
 * 或“timestamp”表示Reader读取UNIX格式的timestamp，或“time”是Reader读取的是javascript毫秒的timestamp。
 * (Optional) A format string for the {@link Date#parseDate Date.parseDate} function,
 * or "timestamp" if the value provided by the Reader is a UNIX timestamp, or "time" if the value provided by the Reader is a 
 * javascript millisecond timestamp.</div></li>
 * 
 * <li><b>defaultValue</b> : Mixed<div class="sub-desc">（可选的）默认值。
 * <b>当经过{@link Ext.data.Reader Reader}创建Record时会使用该值；</b> 当<b><tt>mapping</tt></b>的引用项不存在的时候，典型的情况为undefined时候会使用该值（默认为''）。
 * (Optional) The default value used <b>when a Record is being created by a
 * {@link Ext.data.Reader Reader}</b> when the item referenced by the <b><tt>mapping</tt></b> does not exist in the data object
 * (i.e. undefined). (defaults to "")</div></li>
 * 
 * </ul>
 * 透过create方法会返回一个构造器的函数，这样就可以用来创建一个一个Record对象了。
 * 数据对象（在第一个参数上）一定要有一个属性，是名为<b>names</b>的属性，以说明是什么字段。
 * The constructor generated by this method may be used to create new Record instances. The data object must contain properties
 * named after the field <b>names</b>. 
 * <br>用法：usage:<br><pre><code>
var TopicRecord = Ext.data.Record.create([
    {name: 'title', mapping: 'topic_title'},
    {name: 'author', mapping: 'username'},
    {name: 'totalPosts', mapping: 'topic_replies', type: 'int'},
    {name: 'lastPost', mapping: 'post_time', type: 'date'},
    {name: 'lastPoster', mapping: 'user2'},
    {name: 'excerpt', mapping: 'post_text'}
]);

var myNewRecord = new TopicRecord({
    title: 'Do my job please',
    author: 'noobie',
    totalPosts: 1,
    lastPost: new Date(),
    lastPoster: 'Animal',
    excerpt: 'No way dude!'
});
myStore.add(myNewRecord);
</code></pre>
 * <p>简单地说，除了 <tt>name</tt>属性是必须的外，其他属性是可以不要的，因此，你只要传入一个字符串就可满足最低条件了，因为它就代表字段名称。
 * In the simplest case, if no properties other than <tt>name</tt> are required, a field definition
 * may consist of just a field name string.</p>
 * @method create
 * @return {function} 根据定义创建新Records的构造器。A constructor which is used to create new Records according
 * to the definition.
 * @static
 */
Ext.data.Record.create = function(o){
    var f = Ext.extend(Ext.data.Record, {});
    var p = f.prototype;
    p.fields = new Ext.util.MixedCollection(false, function(field){
        return field.name;
    });
    for(var i = 0, len = o.length; i < len; i++){
        p.fields.add(new Ext.data.Field(o[i]));
    }
    f.getField = function(name){
        return p.fields.get(name);
    };
    return f;
};

Ext.data.Record.AUTO_ID = 1000;
Ext.data.Record.EDIT = 'edit';
Ext.data.Record.REJECT = 'reject';
Ext.data.Record.COMMIT = 'commit';

Ext.data.Record.prototype = {
    /**
     * <p><b>
     * Record定义的<u>prototype</u>保存了该属性。
     * This property is stored in the Record definition's <u>prototype</u></b></p>
     * 为该Record服务的{@link Ext.data.Field Field}，存储在一个MixedCollection对象中。只读的。
     * A MixedCollection containing the defined {@link Ext.data.Field Field}s for this Record.  Read-only.
     * @property fields
     * @type Ext.util.MixedCollection
     */
    /**
     * Record的数据对象。Record定义中的所有字段名就表示该对象属性身上的名称。
     * 注意除非你指定了一个字段的名称为"id"在Record定义中，否则这<b>不会</b>包含一个<tt>id</tt>属性。
     * An object hash representing the data for this Record. Every field name in the Record definition
     * is represented by a property of that name in this object. Note that unless you specified a field
     * with name "id" in the Record definition, this will <b>not</b> contain an <tt>id</tt> property.
     * @property data
     * @type {Object}
     */
    /**
     * 惟一的ID，在Record创建时分派此值。
     * The unique ID of the Record as specified at construction time.
     * @property id
     * @type {Object}
     */
    /**
     * 只读。true表示为Record有修改。
     * Readonly flag - true if this Record has been modified.
     * @type Boolean
     */
    dirty : false,
    editing : false,
    error: null,
    /**
     * 该对象保存了所有修改过的字段的原始值数据（键和值key and Value）。
	 * 如果没有字段被修改的话，该值是null。
     * This object contains a key and value storing the original values of all modified fields or is null if no fields have been modified.
     * @property modified
     * @type {Object}
     */
    modified: null,

    // private
    join : function(store){
        /**
         * 该Record所属的{@link Ext.data.Store}对象。
         * The {@link Ext.data.Store} to which this Record belongs.
         * @property store
         * @type {Ext.data.Store}
         */
        this.store = store;
    },

    /**
     * 根据字段设置值。
     * Set the named field to the specified value.
     * @param {String} name 字段名称的字符串。The name of the field to set.
     * @param {Object} value 值。The value to set the field to.
     */
    set : function(name, value){
        if(String(this.data[name]) == String(value)){
            return;
        }
        this.dirty = true;
        if(!this.modified){
            this.modified = {};
        }
        if(typeof this.modified[name] == 'undefined'){
            this.modified[name] = this.data[name];
        }
        this.data[name] = value;
        if(!this.editing && this.store){
            this.store.afterEdit(this);
        }
    },

    /**
     * 根据字段返回值。
     * Get the value of the named field.
     * @param {String} name 字段名称的字符串。The name of the field to get the value of.
     * @return {Object} 字段的值。The value of the field.
     */
    get : function(name){
        return this.data[name];
    },

    /**
     * 开始进入编辑。编辑期间，没有与所在的store任何关联的事件。
     * Begin an edit. While in edit mode, no events are relayed to the containing store.
     */
    beginEdit : function(){
        this.editing = true;
        this.modified = this.modified || {};
    },

    /**
     * 取消所有已修改过的数据。
     * Cancels all changes made in the current edit operation.
     */
    cancelEdit : function(){
        this.editing = false;
        delete this.modified;
    },

    /**
     * 结束编辑。如数据有变动，则会通知所在的store。
     * End an edit. If any data was modified, the containing store is notified.
     */
    endEdit : function(){
        this.editing = false;
        if(this.dirty && this.store){
            this.store.afterEdit(this);
        }
    },

    /**
     * @param {Boolean} silent 
     * 这个方法通常给Record对象所在的那个{@link Ext.data.Store}对象调用。
     * 创建Record、或上一次提交的操作都会使得Record对象执行reject撤销方法。
     * 原始值会变化为已修改的值。
     * Usually called by the {@link Ext.data.Store} which owns the Record.
     * Rejects all changes made to the Record since either creation, or the last commit operation.
     * Modified fields are reverted to their original values.
     * <p>
     * 要根据提交（commit）操作而传达的通知，开发人员应该登记{@link Ext.data.Store#update}事件来编码来执行特定的撤销操作
     * Developers should subscribe to the {@link Ext.data.Store#update} event to have their code notified
     * of reject operations.</p>
     * @param {Boolean} silent （可选的）True表示不通知自身的store对象有所改变（默认为false）。(optional) True to skip notification of the owning store of the change (defaults to false)
     */
    reject : function(silent){
        var m = this.modified;
        for(var n in m){
            if(typeof m[n] != "function"){
                this.data[n] = m[n];
            }
        }
        this.dirty = false;
        delete this.modified;
        this.editing = false;
        if(this.store && silent !== true){
            this.store.afterReject(this);
        }
    },

    /**
     * 这个方法通常给Record对象所在的那个{@link Ext.data.Store}对象调用。
     * 创建Record、或上一次提交的操作都会使得Record对象执行commit提交方法。
     * Usually called by the {@link Ext.data.Store} which owns the Record.
     * Commits all changes made to the Record since either creation, or the last commit operation.
     * <p>
     * 要根据提交（commit）操作而传达的通知，开发人员应该登记{@link Ext.data.Store#update}事件来编码来执行特定的更新操作。</p>
     * Developers should subscribe to the {@link Ext.data.Store#update} event to have their code notified
     * of commit operations.
     * </p>
     * @param {Boolean} silent （可选的）True表示不通知自身的store对象有所改变（默认为false）。(optional) True to skip notification of the owning store of the change (defaults to false)
     */
    commit : function(silent){
        this.dirty = false;
        delete this.modified;
        this.editing = false;
        if(this.store && silent !== true){
            this.store.afterCommit(this);
        }
    },

    /**
     * 该对象被创建或提交之后，用来获取字段的哈希值（hash）。
     * Gets a hash of only the fields that have been modified since this Record was created or commited.
     * @return Object
     */
    getChanges : function(){
        var m = this.modified, cs = {};
        for(var n in m){
            if(m.hasOwnProperty(n)){
                cs[n] = this.data[n];
            }
        }
        return cs;
    },

    // private
    hasError : function(){
        return this.error != null;
    },

    // private
    clearError : function(){
        this.error = null;
    },

    /**
     * 创建记录的副本。
     * Creates a copy of this Record.
     * @param {String} id （可选的）创建新的ID如果你不想在ID上也雷同。(optional) A new Record id if you don't want to use this Record's id
     * @return {Record}
     */
    copy : function(newId) {
        return new this.constructor(Ext.apply({}, this.data), newId || this.id);
    },

    /**
     * 如果传入的字段是修改过的（load或上一次提交）就返回true。
     * Returns true if the field passed has been modified since the load or last commit.
     * @param {String} fieldName
     * @return {Boolean}
     */
    isModified : function(fieldName){
        return !!(this.modified && this.modified.hasOwnProperty(fieldName));
    }
};